#include "commsInterface.hpp"
//#include "../../tAutomaLibCpp/tAutomaLibCppUtils.hpp"
#include "SerialPortManager/SerialPortManager.hpp"
#include "MiscUtils/TdrMisc/SEngine.h"

using namespace std;

int CommsInterface::pendingPortCBRefIdx = -1;
CommDataCallbackMP CommsInterface::pendingPortSentCB;
CommDataCallbackMP CommsInterface::pendingPortReceiveCB;

struct SerialPortGroup {
	SerialPortGroup(std::vector<SerialPortManager*> portsIn) { ports = portsIn; };
	~SerialPortGroup();
	std::vector<SerialPortManager*> ports;
};

SerialPortGroup::~SerialPortGroup() {
	for (SerialPortManager*& port : ports) {
		if (port != nullptr) {
			port->disconnect();
			delete port;
		}
	}
}

std::string CommsInterface::ClassHint = "[Comm] ";
bool CommsInterface::flagSimu = false;

void CommsInterface::setSimuMode(bool newFlag) {
	flagSimu = newFlag;
	if (flagSimu) {
		ClassHint = "[CommSim]";
	}
}

void* CommsInterface::searchForSlavesOverSerial(bool simuThis, std::vector<std::wstring> hardwareIds,
	int baudRate, int parity, int dataBitNum, int stopBitNum, HandShakeProtocol& handshake) {
	TLogger logger(ClassHint + "searchForSlavesOverSerial", true);
	if (!flagSimu && (!simuThis)) {
		SerialPortGroup portGroup = SerialPortGroup(SerialPortManager::listPortsWithHardwareIds(hardwareIds));
		TLogger::writeLine("Discovered candidates: " + TFormater::int2str(portGroup.ports.size()));
		for (SerialPortManager*& port : portGroup.ports) {
			TLogger logger("Explore " + port->getPortName(), true);
			bool portMatch = true;
			if (port->config(baudRate, parity, dataBitNum, stopBitNum)) {
				TLogger logger("Handshake");
				port->purge();

				port->_callback.referenceIndex = pendingPortCBRefIdx;
				port->_callback.sentCallbackMP = pendingPortSentCB;
				port->_callback.receiveCallbackMP = pendingPortReceiveCB;

				portMatch = CommChannelSerial::executeHandshake(port, handshake, 0);

				if (portMatch) {
					SerialPortManager* ret = port;
					// not to release
					port = nullptr;
					TLogger::writeLine("Found match");
					return ret;
				}
			}
			else {
				TLogger::writeLine("Cannot open");
			}
		}
		return nullptr;
	}
	else {
		// simu mode. just report
		SerialPortManager* ret = new SerialPortManager();

		ret->_callback.referenceIndex = pendingPortCBRefIdx;
		ret->_callback.sentCallbackMP = pendingPortSentCB;
		ret->_callback.receiveCallbackMP = pendingPortReceiveCB;

		ret->simulateConnect(handshake, true, baudRate, parity, dataBitNum, stopBitNum);
		//TLogger::writeLine("Look for candi: " + TFormater::int2str(portGroup.ports.size()));

		return ret;
	}
}

void CommsInterface::searchForSlavesOverEthernet(std::vector<std::vector<uint8_t>>& hardwareIds) {

}

void* CommsInterface::discoverNextOverEthernet() {
	return nullptr;
}

void CommChannel::setSimu() {
	flagSimu = true;
}

bool CommChannel::isSimu() {
	return flagSimu;
};

CommChannel* CommChannel::shallowCopy(CommChannel* ref) {
	if (ref->getInterfaceType().compare("serial") == 0) {
		CommChannelSerial* ret = new CommChannelSerial();
		ret->shareManager((CommChannelSerial*)ref);
		return ret;
	}
	return nullptr;
}

CommChannelSerial::CommNative::~CommNative() {
	//if (isReference == 0) {
	if (serialManager != nullptr) {
		delete serialManager;
		serialManager = nullptr;
	}
	//}
}

void CommChannelSerial::setManager(void* managerPtr) {
	_commNat.serialManager = managerPtr;
}

void CommChannelSerial::shareManager(CommChannelSerial* channel) {
	auto manager = new SerialPortManager();
	manager->makeShallowCopy((SerialPortManager*)channel->_commNat.serialManager);
	setManager(manager);
}

std::string CommChannelSerial::toStr() {
	std::string ret;

	if (_commNat.serialManager == nullptr) {
		ret += "Port: Simulated;";
	}
	else {
		SerialPortManager* mPtr = (SerialPortManager*)_commNat.serialManager;
		ret += "Port: " + mPtr->getPortName() + ";";
		ret += "Config: " + mPtr->getConnectionConfig() + ";";
	}

	return ret;
}

int CommChannelSerial::isConnected() {
	if (_commNat.serialManager == nullptr) {
		return -1;
	}
	return ((SerialPortManager*)_commNat.serialManager)->isConnected();
}

int CommChannelSerial::sendSignal(std::string signalName, int signalVal) {
	if (_commNat.serialManager == nullptr) {
		return -1;
	}
	SerialPortManager* ptr = (SerialPortManager*)_commNat.serialManager;
	if (signalName.compare("dtr")) {
		return (int)ptr->setDtr(signalVal);
	}
	if (signalName.compare("rts")) {
		return (int)ptr->setRts(signalVal);
	}
	return -1;
}

int CommChannelSerial::sendBytes(std::vector<uint8_t> bytes) {
	if ((!isSimu()) && (_commNat.serialManager == nullptr)) {
		return -1;
	}
	SerialPortManager* ptr = (SerialPortManager*)_commNat.serialManager;
	// call back in 
	// GeneralSlave::CommCallback
	// TAutomaLogs::logSerialSent
	return (int)ptr->send(bytes);
}

int CommChannelSerial::sendString(std::string str) {
	if ((!isSimu()) && (_commNat.serialManager == nullptr)) {
		return -1;
	}
	SerialPortManager* ptr = (SerialPortManager*)_commNat.serialManager;
	// call back in 
	// GeneralSlave::CommCallback
	// TAutomaLogs::logSerialSent
	return (int)ptr->send(str);
}

std::string CommChannelSerial::receiveStrReplyWithDelim(int timeoutMs, char delim) {
	if (_commNat.serialManager == nullptr) {
		return "";
	}
	SerialPortManager* ptr = (SerialPortManager*)_commNat.serialManager;
	string tmp = "a";
	tmp[0] = delim;
	return ptr->receiveStr("", tmp, vector<uint8_t>(), timeoutMs);
}

std::string CommChannelSerial::getHardware() {
	if (isSimu()) {
		return "Simulated";
	}
	if (_commNat.serialManager == nullptr) {
		return "NA";
	}
	SerialPortManager* ptr = (SerialPortManager*)_commNat.serialManager;
	return ptr->getPortName();
}

int CommChannelSerial::disconnect() {
	if (_commNat.serialManager == nullptr) {
		return -1;
	}
	SerialPortManager* ptr = (SerialPortManager*)_commNat.serialManager;
	ptr->disconnect();
	delete ptr;
	ptr = nullptr;
	return 1;
}

void CommChannelSerial::purge() {
	TLogger block("Pg");
	if (_commNat.serialManager == nullptr) {
		return;
	}
	SerialPortManager* ptr = (SerialPortManager*)_commNat.serialManager;
	TLogger::writeLine("Pg 1");
	ptr->purge();
}

int CommChannelSerial::reconnect() {
	if (_commNat.serialManager == nullptr) {
		return 1;
	}

	SerialPortManager* ptr = (SerialPortManager*)_commNat.serialManager;
	return ptr->reconnect();
}

//int CommChannelSerial::setCB(CommDataCallback sentCB, CommDataCallback receiveCB) {
//	if (serialManager == nullptr) {
//		return 0;
//	}
//	SerialPortManager* ptr = (SerialPortManager*)serialManager;
//
//	ptr->_callback.sentCallbackMP = sentCB;
//	ptr->_callback.receiveCallbackMP = receiveCB;
//
//	return 1;
//}

int CommChannelSerial::setMPCB(int mpIndex, CommDataCallbackMP sentCB, CommDataCallbackMP receiveCB) {
	if (_commNat.serialManager == nullptr) {
		return 0;
	}
	SerialPortManager* ptr = (SerialPortManager*)_commNat.serialManager;

	ptr->_callback.referenceIndex = mpIndex;
	ptr->_callback.sentCallbackMP = sentCB;
	ptr->_callback.receiveCallbackMP = receiveCB;

	return 1;
}

int CommChannelSerial::executeHandshake(HandShakeProtocol& handshake, int softTimeout) {
	return executeHandshake(_commNat.serialManager, handshake, softTimeout);
}

int CommChannelSerial::executeHandshake(void* serialManager, HandShakeProtocol& handshake, int softTimeout) {
	SerialPortManager* port = (SerialPortManager*)serialManager;
	//port->purge();
	if (port->isSimu()) {
		handshake.restart();
		port->simulateHandshake(handshake);
		return 1;
	}

	int portMatch = true;

	handshake.restart();
	for (int idx = 0; idx < 20; idx++) {
		if (port->peakBytes().size() > 0) {
			//port->purge();
			Sleep(100);
		}
		else {
			break;
		}
	}

	port->purge();
	for (auto& shake : handshake.handshakes) {
		if (shake.toSend.dataToSend.size() > 0) {
			if (!port->send(&shake.toSend.dataToSend[0], shake.toSend.dataToSend.size())) {
				portMatch = false;
				TLogger::writeLine("Send failed");
				break;
			}
		}
		shake.result.reply = port->receiveUntil(shake.exp.replyEnd, shake.exp.timeout * 2);
		if (shake.exp.flagParseNumberInReply[0] > 0) {
			//Sleep(3000);
			continue;
		}

		shake.result.replyTypeIdxOut = TFormater::matchTemplates(shake.result.reply, shake.exp.possibleReplies);
		if (shake.result.replyTypeIdxOut < 0) {
			if (softTimeout > 0) {
				auto start = clock();
				TLogger::writeLine("Receive error but soft timeout set. Wait for data catch up");
				//if (TFormater::beginWith(shake.result.reply, "$")) {
				//	TLogger::writeLine("A missed config!");
				//	Sleep(100);
				//	port->purge();
				//	return -1;
				//}
				while (clock() - start < softTimeout) {
					Sleep(10);
					while (port->peakBytes().size() > 0) {
						shake.result.reply = port->receiveUntil(shake.exp.replyEnd, shake.exp.timeout);
						if (shake.result.reply.size() == 0) {
							break;
						}
					}
					// parse all data
					shake.result.replyTypeIdxOut = TFormater::matchTemplates(shake.result.reply, shake.exp.possibleReplies);
					if (shake.result.replyTypeIdxOut >= 0) {
						TLogger::writeLine("Retry ok");
						break;
					}
				}
			}
		}


		portMatch &= (shake.result.replyTypeIdxOut >= 0);
		if (!portMatch) {
			TLogger::writeLine("Not match. ");
			TLogger::writeLine("Over " + port->getPortName());
			TLogger::writeLine("Expected: " + TFormater::toDisplayable(shake.exp.possibleReplies[0]));
			TLogger::writeLine("Received: " + TFormater::toDisplayable(shake.result.reply));
			break;
		}
	}

	port->purge();
	return portMatch;
}

void CommChannelSerial::prepare() {
	SerialPortManager::resetUSBDevices();
}

std::vector<std::wstring> HardwareList::getSerialHWIDs() {
	std::vector<std::wstring> ret;

	// ch340
	ret.push_back(L"USB\\VID_1A86&PID_7523&REV_8132");
	ret.push_back(L"USB\\VID_1A86&PID_7523&REV_8133");
	ret.push_back(L"USB\\VID_1A86&PID_7523&REV_8134");
	// ch343
	ret.push_back(L"USB\\VID_1A86&PID_55D3&REV_0442");
	// CP210x
	ret.push_back(L"USB\\VID_10C4&PID_EA60&REV_0100");

	return ret;
}